package info.scholtus.open.provider;

import static info.scholtus.open.util.LogUtils.LOGV;
import static info.scholtus.open.util.LogUtils.makeLogTag;
import info.scholtus.open.provider.Contract.Blocks;
import info.scholtus.open.provider.Contract.Categories;
import info.scholtus.open.provider.Contract.Properties;
import info.scholtus.open.provider.Contract.Sites;
import info.scholtus.open.provider.Contract.Vendors;
import info.scholtus.open.provider.Database.Tables;
import info.scholtus.open.util.SelectionBuilder;

import java.util.Arrays;

import android.app.Activity;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

/**
 * Provider that stores {@link Contract} data. Data is usually inserted
 * by TODO: me {@link info.scholtus.android.apps.open.sync.SyncHelper}, 
 * and queried by various {@link Activity} instances.
 */
public class Provider extends ContentProvider {

    private static final String TAG = makeLogTag(Provider.class);

    private Database mOpenHelper;

    private static final UriMatcher sUriMatcher = buildUriMatcher();

    private static final int CATEGORIES = 100;
    private static final int CATEGORIES_ID = 101;
    private static final int CATEGORIES_ID_VENDORS = 102;

    private static final int VENDORS = 200;
    private static final int VENDORS_ID = 201;
    private static final int VENDORS_ID_SITES = 202;

    private static final int SITES = 300;
    private static final int SITES_ID = 301;
    private static final int SITES_ID_PROPERTIES = 302;
    private static final int SITES_ID_BLOCKS = 303;

    private static final int PROPERTIES = 400;
    private static final int PROPERTIES_ID = 401;
    
    private static final int BLOCKS = 500;
    private static final int BLOCKS_ID = 501;

    /** {@inheritDoc} */
    @Override
	public boolean onCreate() {
        mOpenHelper = new Database(getContext());
        return true;
	}

	/** {@inheritDoc} */
    @Override
	public String getType(Uri uri) {
        final int match = sUriMatcher.match(uri);
        switch (match) {
	        case CATEGORIES:
	            return Categories.CONTENT_TYPE;
	        case CATEGORIES_ID:
	            return Categories.CONTENT_ITEM_TYPE;
	        case VENDORS:
	            return Vendors.CONTENT_TYPE;
	        case VENDORS_ID:
	            return Vendors.CONTENT_ITEM_TYPE;
	        case SITES:
	            return Sites.CONTENT_TYPE;
	        case SITES_ID:
	            return Sites.CONTENT_ITEM_TYPE;
            case PROPERTIES:
                return Properties.CONTENT_TYPE;
            case PROPERTIES_ID:
                return Properties.CONTENT_ITEM_TYPE;
            case BLOCKS:
                return Blocks.CONTENT_TYPE;
            case BLOCKS_ID:
                return Blocks.CONTENT_ITEM_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
	}
    
    /** {@inheritDoc} */
    @Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
    	LOGV(TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
        final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        return buildSimpleSelection(uri)
        		.where(selection, selectionArgs)
        		.query(db, projection, sortOrder);
	}

	/** {@inheritDoc} */
    @Override
	public Uri insert(Uri uri, ContentValues values) {
    	LOGV(TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")");
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        switch (match) {
	        case CATEGORIES: {
	        	values.put(Categories.CATEGORY_ID, Categories.generateCategoryId(values.getAsString(Categories.CATEGORY_NAME)));
	            db.insertOrThrow(Tables.CATEGORIES, null, values);
	            getContext().getContentResolver().notifyChange(uri, null);
	            return Categories.buildCategoryUri(values.getAsString(Categories.CATEGORY_ID));
	    	}
	        case VENDORS: {
	            db.insertOrThrow(Tables.VENDORS, null, values);
	            getContext().getContentResolver().notifyChange(uri, null);
	            return Vendors.buildVendorUri(values.getAsString(Vendors.VENDOR_ID));
	        }
	        case SITES: {
	            db.insertOrThrow(Tables.SITES, null, values);
	            getContext().getContentResolver().notifyChange(uri, null);
	            return Sites.buildSiteUri(values.getAsString(Sites.SITE_ID));
	        }
	        case PROPERTIES: {
	            db.insertOrThrow(Tables.PROPERTIES, null, values);
	            getContext().getContentResolver().notifyChange(uri, null);
	            return Properties.buildPropertyUri(values.getAsString(Properties.PROPERTY_ID));
	        }
            case BLOCKS: {
                db.insertOrThrow(Tables.BLOCKS, null, values);
                getContext().getContentResolver().notifyChange(uri, null);
                return Blocks.buildBlockUri(values.getAsString(Blocks.BLOCK_ID));
            }
            default: {
                throw new UnsupportedOperationException("Unknown uri: " + uri);
            }
        }
	}
	
    /** {@inheritDoc} */
    @Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
        LOGV(TAG, "update(uri=" + uri + ", values=" + values.toString() + ")");
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int retVal = buildSimpleSelection(uri).where(selection, selectionArgs).update(db, values);
        getContext().getContentResolver().notifyChange(uri, null);
        return retVal;
	}

    /** {@inheritDoc} */
    @Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
        LOGV(TAG, "delete(uri=" + uri + ")");
        if (uri == Contract.BASE_CONTENT_URI) {
            // Handle whole database deletes (e.g. when signing out)
            deleteDatabase();
            getContext().getContentResolver().notifyChange(uri, null, false);
            return 1;
        }
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final SelectionBuilder builder = buildSimpleSelection(uri);
        int retVal = builder.where(selection, selectionArgs).delete(db);
        getContext().getContentResolver().notifyChange(uri, null);
        return retVal;
	}

    private void deleteDatabase() {
        // TODO: wait for content provider operations to finish, then tear down
        mOpenHelper.close();
        Context context = getContext();
        Database.deleteDatabase(context);
        mOpenHelper = new Database(getContext());
    }
    
	/**
     * Build and return a {@link UriMatcher} that catches all {@link Uri}
     * variations supported by this {@link Provider}.
     */
    private static UriMatcher buildUriMatcher() {
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = Contract.CONTENT_AUTHORITY;

        matcher.addURI(authority, "categories", CATEGORIES);
        matcher.addURI(authority, "categories/*", CATEGORIES_ID);
        matcher.addURI(authority, "categories/*/vendors", CATEGORIES_ID_VENDORS);

        matcher.addURI(authority, "vendors", VENDORS);
        matcher.addURI(authority, "vendors/*", VENDORS_ID);
        matcher.addURI(authority, "vendors/*/sites", VENDORS_ID_SITES);

        matcher.addURI(authority, "sites", SITES);
        matcher.addURI(authority, "sites/*", SITES_ID);
        matcher.addURI(authority, "sites/*/properties", SITES_ID_PROPERTIES);
        matcher.addURI(authority, "sites/*/blocks", SITES_ID_BLOCKS);
        
        matcher.addURI(authority, "properties", PROPERTIES);
        matcher.addURI(authority, "properties/*", PROPERTIES_ID);
        
        matcher.addURI(authority, "blocks", BLOCKS);
        matcher.addURI(authority, "blocks/*", BLOCKS_ID);

        return matcher;
    }
	
    /**
     * Build a simple {@link SelectionBuilder} to match the requested
     * {@link Uri}. This is usually enough to support {@link #insert},
     * {@link #update}, and {@link #delete} operations.
     */
    private SelectionBuilder buildSimpleSelection(Uri uri) {
        final SelectionBuilder builder = new SelectionBuilder();
        final int match = sUriMatcher.match(uri);
        switch (match) {
	        case CATEGORIES: {
	            return builder.table(Tables.CATEGORIES);
	        }
	        case CATEGORIES_ID: {
	            final String categoryId = Categories.getCategoryId(uri);
	            return builder.table(Tables.CATEGORIES)
	                    .where(Categories.CATEGORY_ID + "=?", categoryId);
	        }
	        case CATEGORIES_ID_VENDORS: {
	            final String categoryId = Categories.getCategoryId(uri);
	            return builder.table(Tables.VENDORS)
	                    .where(Categories.CATEGORY_ID + "=?", categoryId);
	        }
	        case VENDORS: {
	            return builder.table(Tables.VENDORS);
	        }
	        case VENDORS_ID: {
	            final String vendorId = Vendors.getVendorId(uri);
	            return builder.table(Tables.VENDORS)
	                    .where(Vendors.VENDOR_ID + "=?", vendorId);
	        }
	        case VENDORS_ID_SITES: {
	            final String vendorId = Vendors.getVendorId(uri);
	            return builder.table(Tables.SITES)
	                    .where(Vendors.VENDOR_ID + "=?", vendorId);
	        }
	        case SITES: {
	            return builder.table(Tables.SITES);
	        }
	        case SITES_ID: {
	            final String siteId = Sites.getSiteId(uri);
	            return builder.table(Tables.SITES)
	                    .where(Vendors.VENDOR_ID + "=?", siteId);
	        }
	        case SITES_ID_PROPERTIES: {
	            final String siteId = Sites.getSiteId(uri);
	            return builder.table(Tables.PROPERTIES)
	                    .where(Sites.SITE_ID + "=?", siteId);
	        }
	        case SITES_ID_BLOCKS: {
	            final String siteId = Sites.getSiteId(uri);
	            return builder.table(Tables.BLOCKS)
	                    .where(Sites.SITE_ID + "=?", siteId);
	        }
	        case BLOCKS: {
	            return builder.table(Tables.BLOCKS);
	        }
	        case BLOCKS_ID: {
	            final String blockId = Blocks.getBlockId(uri);
	            return builder.table(Tables.BLOCKS)
	                    .where(Blocks.BLOCK_ID + "=?", blockId);
	        }
	        default: {
	            throw new UnsupportedOperationException("Unknown uri: " + uri);
	        }
        }
    }
}
